/*
 * Copyright (C) 2009 aileron.cc
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.aileron.orm.factory;

import static cc.aileron.orm.factory.G2DaoExecuteInvoker.Category.*;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cc.aileron.accessor.PojoAccessor;
import cc.aileron.accessor.PojoAccessorManager;
import cc.aileron.accessor.PojoAccessorValueNotFoundException;
import cc.aileron.accessor.PojoPropertiesNotFoundException;
import cc.aileron.commons.instance.InstanceManager;
import cc.aileron.commons.resource.ResourceNotFoundException;
import cc.aileron.commons.util.SkipList;
import cc.aileron.generic.ObjectContainer;
import cc.aileron.generic.Paging;
import cc.aileron.generic.Procedure;
import cc.aileron.orm.G2DaoFinder;
import cc.aileron.orm.G2DaoPrivate;
import cc.aileron.orm.factory.G2DaoExecuteInvoker.Category;
import cc.aileron.orm.factory.serial.G2DaoSerial;
import cc.aileron.orm.sql.G2DaoSqlMap;
import cc.aileron.template.parser.ParserMethodNotFoundException;
import cc.aileron.template.reader.TemplateSyntaxEexception;

import com.google.inject.Inject;
import com.google.inject.Provider;

/**
 * @author Aileron
 */
public class G2DaoFactoryImpl implements G2DaoFactory
{
    /**
     * logger
     */
    static final Logger logger = LoggerFactory.getLogger(G2DaoFactory.class);

    @Override
    public <T> G2DaoPrivate<T> create(final Class<T> targetClass) throws IOException,
            ResourceNotFoundException,
            TemplateSyntaxEexception,
            ParserMethodNotFoundException
    {
        final G2DaoSerial serial = this.serial;
        final G2DaoSqlMap sqlMap = this.sqlMap;
        sqlMap.compile(targetClass);
        return new G2DaoPrivate<T>()
        {
            @Override
            public int delete(final T bean)
            {
                return execute(DELETE, bean);
            }

            @Override
            public G2DaoFinder<T> find(final Object condition)
            {
                if (condition == null)
                {
                    throw new IllegalArgumentException("condition is null");
                }
                final PojoAccessorManager manager = this.manager;
                final G2DaoTransactionManager transactionManager = this.transactionManager;
                final Provider<PojoAccessor<T>> provider = this.provider;
                final G2DaoExecuteInvoker<T> executor = this.executor;
                return new G2DaoFinder<T>()
                {
                    @Override
                    public void bind(final T object)
                    {
                        if (object == null)
                        {
                            throw new IllegalArgumentException(
                                    "bindable object is null");
                        }
                        final PojoAccessor<T> accessor = manager.from(object);
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final List<String> columnNames = getColumnNames(resultSet);
                                if (!resultSet.next())
                                {
                                    return;
                                }
                                bind(accessor, columnNames, resultSet);
                            }
                        });
                    }

                    @Override
                    public int count()
                    {
                        final ObjectContainer<Integer> c = new ObjectContainer<Integer>(
                                0);
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException
                            {
                                if (resultSet.last())
                                {
                                    c.value = resultSet.getRow();
                                }
                            }
                        });
                        return c.value;
                    }

                    @Override
                    public void each(final Procedure<T> procedure)
                    {
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final List<String> columnNames = getColumnNames(resultSet);
                                while (resultSet.next())
                                {
                                    final PojoAccessor<T> accessor = provider.get();
                                    bind(accessor, columnNames, resultSet);
                                    final T target = accessor.toTarget();
                                    procedure.call(target);
                                }
                            }
                        });
                    }

                    @Override
                    public void each(final Procedure<T> procedure,
                            final Paging paging)
                    {
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final List<String> columnNames = getColumnNames(resultSet);
                                final int offset = paging.offset();
                                final int limit = paging.limit();

                                resultSet.absolute(offset);

                                int i = 0;
                                while (resultSet.next())
                                {
                                    i += 1;

                                    if (limit < i)
                                    {
                                        resultSet.close();
                                        break;
                                    }

                                    final PojoAccessor<T> accessor = provider.get();
                                    bind(accessor, columnNames, resultSet);
                                    final T target = accessor.toTarget();
                                    procedure.call(target);
                                }
                            }
                        });
                    }

                    @Override
                    public boolean exist()
                    {
                        final ObjectContainer<Boolean> c = new ObjectContainer<Boolean>(
                                false);
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException
                            {
                                c.value = resultSet.next();
                            }
                        });
                        return c.value;
                    }

                    @Override
                    public List<T> list()
                    {
                        final ObjectContainer<List<T>> c = new ObjectContainer<List<T>>(
                                new SkipList<T>());
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final List<String> columnNames = getColumnNames(resultSet);
                                while (resultSet.next())
                                {
                                    final PojoAccessor<T> accessor = provider.get();
                                    bind(accessor, columnNames, resultSet);
                                    c.value.add(accessor.toTarget());
                                }
                            }
                        });
                        return c.value;
                    }

                    @Override
                    public List<T> list(final Paging paging)
                    {
                        final ObjectContainer<List<T>> c = new ObjectContainer<List<T>>(
                                new SkipList<T>());
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final List<String> columnNames = getColumnNames(resultSet);
                                final int offset = paging.offset();
                                final int limit = paging.limit();

                                resultSet.absolute(offset);

                                int i = 0;
                                while (resultSet.next())
                                {
                                    i += 1;

                                    if (limit < i)
                                    {
                                        resultSet.close();
                                        break;
                                    }

                                    final PojoAccessor<T> accessor = provider.get();
                                    bind(accessor, columnNames, resultSet);
                                    c.value.add(accessor.toTarget());
                                }
                            }
                        });
                        return c.value;
                    }

                    @Override
                    public T one()
                    {
                        final ObjectContainer<T> c = new ObjectContainer<T>();
                        execute(condition, new G2DaoInvoker()
                        {
                            @Override
                            public void execute(final ResultSet resultSet) throws SQLException,
                                    PojoAccessorValueNotFoundException,
                                    PojoPropertiesNotFoundException
                            {
                                final PojoAccessor<T> accessor = provider.get();
                                final List<String> columnNames = getColumnNames(resultSet);
                                if (!resultSet.next())
                                {
                                    return;
                                }
                                bind(accessor, columnNames, resultSet);
                                c.value = accessor.toTarget();
                            }
                        });
                        return c.value;
                    }

                    /**
                     * @param invoker
                     * @param condition
                     */
                    private void execute(final Object condition,
                            final G2DaoInvoker invoker)
                    {
                        final Connection connection = transactionManager.get();
                        try
                        {
                            executor.execute(connection, condition, invoker);
                        }
                        catch (final SQLException e)
                        {
                            throw new Error(e);
                        }
                        catch (final PojoPropertiesNotFoundException e)
                        {
                            throw new Error(e);
                        }
                        catch (final PojoAccessorValueNotFoundException e)
                        {
                            throw new Error(e);
                        }
                        finally
                        {
                            transactionManager.close(connection);
                        }
                    }
                };
            }

            @Override
            public void initialize()
            {
                execute(INITIALIZE, null);
            }

            @Override
            public int insert(final T bean)
            {
                return execute(INSERT, bean);
            }

            @Override
            public String toString()
            {
                return ReflectionToStringBuilder.toString(this);
            }

            @Override
            public Class<T> type()
            {
                return targetClass;
            }

            @Override
            public int update(final T bean)
            {
                return execute(UPDATE, bean);
            }

            /**
             * @param category
             * @param bean
             * @return count
             */
            private int execute(final Category category,
                    final T bean)
            {
                final Connection connection = transactionManager.get();
                try
                {
                    final boolean isAutoCommit = connection.getAutoCommit();
                    logger.trace("execute is auto commit {}", isAutoCommit);
                    return executor.execute(category, connection, bean);
                }
                catch (final SQLException e)
                {
                    throw new Error(e);
                }
                catch (final PojoAccessorValueNotFoundException e)
                {
                    throw new Error(e);
                }
                catch (final PojoPropertiesNotFoundException e)
                {
                    throw new Error(e);
                }
                finally
                {
                    transactionManager.close(connection);
                }
            }

            /**
             * @param connectionProvider
             * @param instance
             */
            @SuppressWarnings("unused")
            @Inject
            void init(final InstanceManager instance)
            {
                this.manager = instance.get(PojoAccessorManager.class);
                this.transactionManager = instance.get(G2DaoTransactionManager.class);
                this.executor = instance.get(G2DaoExecutorFactory.class)
                    .get(targetClass, serial, sqlMap, instance);
                final PojoAccessorManager factory = this.manager;
                this.provider = new Provider<PojoAccessor<T>>()
                {
                    @Override
                    public PojoAccessor<T> get()
                    {
                        return factory.from(targetClass);
                    }
                };
            }

            /**
             * executor
             */
            private G2DaoExecuteInvoker<T> executor;

            /**
             * pojo-accessor-factory
             */
            private PojoAccessorManager manager;

            /**
             * accessor
             */
            private Provider<PojoAccessor<T>> provider;

            /**
             * connection-provider
             */
            private G2DaoTransactionManager transactionManager;

        };
    }

    /**
     * @param sqlMap
     * @param serial
     */
    @Inject
    public G2DaoFactoryImpl(final G2DaoSqlMap sqlMap, final G2DaoSerial serial)
    {
        this.sqlMap = sqlMap;
        this.serial = serial;
    }

    /**
     * serial
     */
    private final G2DaoSerial serial;

    /**
     * sqlMap
     */
    private final G2DaoSqlMap sqlMap;
}